package repl

import (
	"bufio"
	"fmt"
	"io"
	"test/compiler"
	"test/lexer"
	"test/object"
	"test/parser"
	"test/vm"
)

const PROMOT = ">> "

func Start(in io.Reader, out io.Writer) {
	scanner := bufio.NewScanner(in)

	var constants []object.Object
	globals := make([]object.Object, vm.GlobalSize)
	symbolTable := compiler.NewSymbolTable()
	for i, v := range object.Builtins {
		symbolTable.DefineBuiltin(i, v.Name)
	}

	for {
		fmt.Fprint(out, PROMOT)
		scanned := scanner.Scan()
		if !scanned {
			return
		}

		line := scanner.Text()
		l := lexer.New(line)
		p := parser.New(l)

		program := p.ParseProgram()
		if len(p.Errors()) != 0 {
			printParseErrors(out, p.Errors())
			continue
		}

		comp := compiler.NewWithState(symbolTable, constants)
		err := comp.Compile(program)
		if err != nil {
			fmt.Fprintf(out, "Woops! Compiling byte code failed:\n%s\n", err)
			continue
		}

		// 把虚拟机指令打出来
		fmt.Fprint(out, "vm Instructions:\n")

		bytecode := comp.Bytecode()
		constants = bytecode.Constants

		fmt.Fprint(out, bytecode.Instructions.String())
		machine := vm.NewWithGlobalStore(bytecode, globals)
		err = machine.Run()
		if err != nil {
			fmt.Fprintf(out, "Woops! Executing bytecode failed:\n%s\n", err)
		}

		stackTop := machine.LastPoppedStackElem()
		if stackTop != nil {
			io.WriteString(out, stackTop.Inspect())
		}
		io.WriteString(out, "\n")
	}
}

func printParseErrors(out io.Writer, errors []string) {
	for _, msg := range errors {
		io.WriteString(out, "\t")
		io.WriteString(out, msg)
		io.WriteString(out, "\n")
	}
}
